﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Statistics;
using NHibernate;

namespace ControleRepresentante.Logic.Repositories
{
    public interface IRepository
    {
        User User { get; }
        Representante GetRepresentante();
    }

    public interface IRepository<TModel, TStatistics> : IRepository
        where TModel : ModelBase
        where TStatistics : IStatistics
    {
        IEnumerable<TModel> GetModels(out TStatistics statistics, int page = -1);
        IEnumerable<TModel> GetModelsByIds(IEnumerable<Guid> ids);
        void AddModel(TModel model);
        TModel GetModelById(Guid id);
        void UpdateModel(TModel model);
        void RemoveModelById(Guid id);
        int PageSize { get; }
    }

    public abstract class GeneralRepositoryBase<TModel, TStatistics, TError> : RepositoryBase<TModel, TStatistics, TError> 
        where TModel : ModelBase where TStatistics : IStatistics
    {
        protected GeneralRepositoryBase(IRepositoryConfiguration repositoryConfiguration, ISession session)
            : base(repositoryConfiguration, session)
        {
        }

        protected virtual void SetProperties(IEnumerable<TModel> models)
        {
        }

        public sealed override IEnumerable<TModel> GetModels(out TStatistics statistics, int page = -1)
        {
            EnsureIsRepresentante();
            IQueryOver<TModel, TModel> queryOver = GetModelsQueryOver();
            int modelCount;
            IEnumerable<TModel> models = FuturePaging(queryOver, page, out modelCount);
            SetProperties(models);
            statistics = GetStatistics(modelCount);
            return models;
        }

        protected abstract TStatistics GetStatistics(int modelCount);

        public sealed override void AddModel(TModel model)
        {
            EnsureIsRepresentante();

            VerifyUniqueness(model);

            using (ITransaction transaction = Session.BeginTransaction())
            {
                OnBeforeSave(model);
                Session.Save(model);
                transaction.Commit();
            }
        }

        protected void VerifyUniqueness(TModel model)
        {
            IQueryOver<TModel, TModel> queryOver = Session.QueryOver<TModel>()
                .Where(x => x.Id != model.Id);
            queryOver = GetUniquenessExpressions(model)
                .Aggregate(queryOver, (current, uniquenessExpression) => current.And(uniquenessExpression));

            if (queryOver.RowCount() != 0)
            {
                ThrowRepositoryException(DuplicatedError);
            }
        }

        public sealed override TModel GetModelById(Guid id)
        {
            EnsureIsRepresentante();

            IQueryOver<TModel, TModel> queryOver = Session.QueryOver<TModel>()
                .Where(x => x.Id == id);
            queryOver = GetModelByIdQueryOver(queryOver, id);
            TModel model = queryOver
                .SingleOrDefault();
            SetProperties(new[] { model });
            return model;
        }

        public sealed override void UpdateModel(TModel model)
        {
            EnsureIsRepresentante();

            VerifyUniqueness(model);

            using (ITransaction transaction = Session.BeginTransaction())
            {
                OnBeforeUpdate(model);
                Session.Update(model);
                transaction.Commit();
            }
        }

        protected virtual void OnBeforeUpdate(TModel model)
        {
        }

        public sealed override void RemoveModelById(Guid id)
        {
            EnsureIsRepresentante();
            using (ITransaction transaction = Session.BeginTransaction())
            {
                TModel model = Session.Get<TModel>(id);
                OnBeforeRemove(model);
                Session.Delete(model);
                transaction.Commit();
            }
        }

        protected virtual void OnBeforeSave(TModel model)
        {
        }

        protected abstract void OnBeforeRemove(TModel model);
        protected abstract IQueryOver<TModel, TModel> GetModelsQueryOver();
        protected abstract IEnumerable<Expression<Func<TModel, bool>>> GetUniquenessExpressions(TModel model);
        protected abstract IQueryOver<TModel, TModel> GetModelByIdQueryOver(IQueryOver<TModel, TModel> queryOver, Guid id);
        protected abstract TError DuplicatedError { get; }
    }
}